package com.yiwyn;

import com.yiwyn.bean.People;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Measurement(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class Main {

    @Param({"100", "1000", "10000", "100000"})
    public int loops;


    @Benchmark
    public void normal() throws Throwable {

        for (int i = 0; i < loops; i++) {
            People people = new People();
            people.setColor("yellow");
        }
    }


    @Benchmark
    public void staticMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = People.getLookup();
        for (int i = 0; i < loops; i++) {
            MethodHandle setter = lookup.findSetter(People.class, "color", String.class);
            People people = new People();
            setter.invoke(people, "yellow");
        }
    }


    @Benchmark
    public void staticShareMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = People.getLookup();
        MethodHandle setter = null;
        for (int i = 0; i < loops; i++) {
            if (setter == null) {
                setter = lookup.findSetter(People.class, "color", String.class);
            }
            People people = new People();
            setter.invoke(people, "yellow");
        }
    }


    @Benchmark
    public void shareMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle setter = null;
        for (int i = 0; i < loops; i++) {
            Field f = People.class.getDeclaredField("color");
            f.setAccessible(true);
            if (setter == null) {
                setter = lookup.unreflectSetter(f);
            }
            People people = new People();
            setter.invoke(people, "yellow");
        }
    }

    @Benchmark
    public void reflectMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        for (int i = 0; i < loops; i++) {
            Field f = People.class.getDeclaredField("color");
            f.setAccessible(true);
            MethodHandle setter = lookup.unreflectSetter(f);
            People people = new People();
            setter.invoke(people, "yellow");
        }
    }


    private static final WeakHashMap<String, MethodHandle> methodHandles = new WeakHashMap<>();

    @Benchmark
    public void weakHashMapMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = People.getLookup();
        for (int i = 0; i < loops; i++) {
            MethodHandle setter = methodHandles
                    .computeIfAbsent("color",
                            s -> {
                                try {
                                    return lookup.findSetter(People.class, s, String.class);
                                } catch (Exception e) {
                                    throw new RuntimeException(e);
                                }
                            });

            People people = new People();
            setter.invoke(people, "yellow");
        }
    }

    private static final WeakHashMap<String, MethodHandle> reflectMethodHandles = new WeakHashMap<>();

    @Benchmark
    public void weakHashMapReflectMethodHandle() throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        for (int i = 0; i < loops; i++) {

            MethodHandle setter = reflectMethodHandles
                    .computeIfAbsent("color",
                            s -> {
                                Field f = null;
                                try {
                                    f = People.class.getDeclaredField(s);
                                    f.setAccessible(true);
                                    return lookup.unreflectSetter(f);
                                } catch (Exception e) {
                                    throw new RuntimeException(e);
                                }
                            });
            People people = new People();
            setter.invoke(people, "yellow");
        }
    }


    @Benchmark
    public void weakHashMapReflectMethodHandleExact() throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        for (int i = 0; i < loops; i++) {

            MethodHandle setter = reflectMethodHandles
                    .computeIfAbsent("color",
                            s -> {
                                Field f = null;
                                try {
                                    f = People.class.getDeclaredField(s);
                                    f.setAccessible(true);
                                    return lookup.unreflectSetter(f);
                                } catch (Exception e) {
                                    throw new RuntimeException(e);
                                }
                            });
            People people = new People();
            setter.invokeExact(people, "yellow");
        }
    }


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Main.class.getSimpleName())
                .resultFormat(ResultFormatType.JSON)
                .addProfiler(GCProfiler.class)
                .build();

        new Runner(opt).run();
    }

}